﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Flight_Reservation.DataLayer;

namespace Flight_Reservation.Algrithm
{
    class GraphAdjecent
    {
        private List<Node> nodes;
        private List<Node> visitedNodes;
        private ReachableNodesList reachableNodes; 
        private List<Edge> edges;

        public GraphAdjecent(List<Node> nodesList, List<Edge> edgesList)
        {
            nodes = nodesList;
            visitedNodes = new List<Node>();
            reachableNodes = new ReachableNodesList();
            edges = edgesList;
            //foreach (Edge edge in edgesList)
            //{
            //    AddEdge(edge.FirstNode.Airport, edge.SecondNode.Airport, edge.Price);
            //}
        }

        public void AddNode(Airport airport)
        {
            Node node = new Node(airport);
            nodes.Add(node);
        }

        //public void AddEdge(Airport startAirport, Airport endAirport, double price)
        //{
        //    Node startNode = nodes.Single(n => n.Airport.AirportCode.Equals(startAirport.AirportCode));
        //    Node endNode = nodes.Single(n => n.Airport.AirportCode.Equals(endAirport.AirportCode));
        //    Edge edge = new Edge(startNode, endNode, price);
        //    edges.Add(edge);

        //    startNode.Edges.Add(edge);
        //    endNode.Edges.Add(edge);

        //}

        public IEnumerable<Route> CheapestRoute(Airport startAirport, Airport endAirport)
        {
            var fullRoute = new Stack<Route>();
            Node start = nodes.Single(n => n.Airport.AirportCode == startAirport.AirportCode);
            Node end = nodes.Single(n => n.Airport.AirportCode == endAirport.AirportCode);

            bool isConnected = FindCheapestRoute(start, end);

            if (!isConnected)
            {
                throw new Exception("There was no flights available");
            }

            Node currentNode = end;
            while (currentNode != start)
            {
                currentNode.Visited = true;
                currentNode.CameFromEdge.Visited = true;

                Route partRoute = new Route();
                partRoute.EndAirport = currentNode.Airport;
                partRoute.StartAirport = currentNode.CameFromEdge.FirstNode.Airport == currentNode.Airport
                    ? currentNode.CameFromEdge.SecondNode.Airport : currentNode.CameFromEdge.FirstNode.Airport;
                partRoute.Price = currentNode.CameFromEdge.Price;

                fullRoute.Push(partRoute);
                currentNode = GetNeighbour(currentNode, currentNode.CameFromEdge);
            }
            end.Visited = true;
            return fullRoute;
        }

        private bool FindCheapestRoute(Node start, Node end)
        {
            Boolean isConnected = true;
            visitedNodes.Clear();
            reachableNodes.Reset();
            Node currentNode = start;
            currentNode.Visited = true;
            start.TotalPrice = 0;
            visitedNodes.Add(currentNode);

            while (currentNode != end && isConnected == true)
            {
                AddReachableNodes(currentNode);
                UpdateReachableNodesTotalPrice(currentNode);

                if (reachableNodes.ReachableNodes.Count == 0)
                {
                    isConnected = false;
                }

                ReachableNode currentReachableNode = reachableNodes.ReachableNodes[0];
                reachableNodes.RemoveReachableNode(currentReachableNode);

                currentNode.Visited = true;
                currentNode = currentReachableNode.Node;

                currentNode.CameFromEdge = currentReachableNode.Edge;
                currentReachableNode.Edge.Visited = true;

                visitedNodes.Add(currentNode);
            }
            return isConnected;
        }

        private void AddReachableNodes(Node node)
        {
            foreach (Edge edge in node.Edges)
            {
                Node neighborNode = GetNeighbour(node, edge);

                Node cameFromNode = node.CameFromEdge != null ? GetNeighbour(node, node.CameFromEdge) : null;
                //if ( Node cameFromNode = node.CameFromEdge != null ) // hvis folk er i tvivl om hvordan overstående skal læses
                //{
                //GetNeighbour(node, node.CameFromEdge)
                //}
                //else{
                // null
                //}
                if ((cameFromNode == null || neighborNode != cameFromNode) && !visitedNodes.Contains(neighborNode))
                {
                    ReachableNode reachableNode;
                    if (reachableNodes.HasNode(neighborNode))
                    {
                        if (node.TotalPrice + edge.Price < neighborNode.TotalPrice)
                        {
                            reachableNode = reachableNodes.GetReachableFromNode(neighborNode);
                            reachableNode.Edge = edge;
                        }
                    }

                    else
                    {
                       reachableNode = new ReachableNode(neighborNode, edge);
                       reachableNodes.AddReachableNode((reachableNode));
                    }
                }
            }
        }

        private void UpdateReachableNodesTotalPrice(Node node)
        {
            double currentPrice = node.TotalPrice;
            foreach (ReachableNode reachableNode in reachableNodes.ReachableNodes)
            {
                if (currentPrice + reachableNode.Edge.Price < reachableNode.Node.TotalPrice ||
                    reachableNode.Node.TotalPrice.Equals(-1))
                {
                    reachableNode.TotalPrice = currentPrice + reachableNode.Edge.Price;
                }
            }
        }

        private static Node GetNeighbour(Node node, Edge edge)
        {
            return edge.FirstNode == node ? edge.SecondNode : edge.FirstNode;
        }
    }
}
